#region License

// This file is part of Grawlix.
// 
// Grawlix is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// Grawlix is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public
// License along with Grawlix.  If not, see
// <http://www.gnu.org/licenses/>.

#endregion

using System;
using System.Diagnostics;
using C5;
using Grawlix.Concurrency;
using Grawlix.Services.Resources;
using log4net;

namespace Grawlix.Services.Rendering
{
	/// <summary>
	/// A factory service for texture objects.
	/// </summary>
	[Serializable]
	public sealed class DefaultTextureFactory :
		AbstractResourceFactory<ITextureResource, Texture>
	{
		/// <summary>
		/// The log used to sent output information.
		/// </summary>
		private static readonly ILog _log =
			LogManager.GetLogger(typeof(DefaultTextureFactory));

		/// <summary>
		/// The currently active computation for a given texture.
		/// </summary>
		private readonly HashDictionary<Uri, Future<WrappedImage>>
			_activeLoads =
				new HashDictionary<Uri, Future<WrappedImage>>();

		/// <summary>
		/// The channel of image load operations.
		/// </summary>
		private readonly FutureChannel<WrappedImage> _channel =
			new FutureChannel<WrappedImage>();

		/// <summary>
		/// The object that creates new texture proxies.
		/// </summary>
		private readonly DefaultTextureFactoryProxyCreator
			_creator = new DefaultTextureFactoryProxyCreator();

		/// <summary>
		/// The service that locates images.
		/// </summary>
		private readonly ITypedResolver<WrappedImage>
			_imageResolver;

		/// <summary>
		/// The texture a computation maps to.
		/// </summary>
		private readonly HashDictionary<Future<WrappedImage>, Uri>
			_mappedTexture =
				new HashDictionary<Future<WrappedImage>, Uri>();

		/// <summary>
		/// The service that handles the graphics context.
		/// </summary>
		private readonly IRenderDevice _renderDevice;

		/// <summary>
		/// The list of expired data, waiting to be unloaded.
		/// </summary>
		private readonly LinkedList<ITextureState> _unloadQueue =
			new LinkedList<ITextureState>();

		/// <summary>
		/// Initializes a new instance of the 
		/// <see cref="DefaultTextureFactory"/> class.
		/// </summary>
		/// <param name="uriResolver">The service which complete URIs.
		/// </param>
		/// <param name="imageResolver">The service which locates
		/// images.</param>
		/// <param name="renderDevice">The service which handles the
		/// graphics context.</param>
		public DefaultTextureFactory(IUriResolver uriResolver,
		                             ITypedResolver<WrappedImage>
		                             	imageResolver,
		                             IRenderDevice renderDevice)
			: base(uriResolver)
		{
			_imageResolver = imageResolver;
			_renderDevice = renderDevice;
		}

		/// <summary>
		/// Access to the object which creates new resource proxies.
		/// </summary>
		/// <value>The creator of new proxies.</value>
		protected internal override
			AbstractResourceProxyCreator<ITextureResource, Texture>
			Creator
		{
			get { return _creator; }
		}

		/// <summary>
		/// Cancels the load of the specified resource.
		/// </summary>
		/// <param name="identifier">The URI of the resource to cancel.</param>
		protected internal override void CancelLoad(Uri identifier)
		{
			Future<WrappedImage> future;
			if (_activeLoads.Remove(identifier, out future))
				future.Cancel();
		}

		/// <summary>
		/// Starts the load of the resource with the given URI.
		/// </summary>
		/// <param name="identifier">The identifier of the resource to
		/// load.</param>
		protected internal override void StartLoad(Uri identifier)
		{
			Future<WrappedImage> future = _channel.Enqueue(() =>
				{
					WrappedImage image = _imageResolver.Load(identifier);
					Debug.Assert(image != null);
					return image;
				});
			_mappedTexture[future] = identifier;
			_activeLoads[identifier] = future;
		}

		/// <summary>
		/// Starts the unload of the given state information.
		/// </summary>
		/// <param name="state">The state information to unload.</param>
		protected internal override void StartUnload(object state)
		{
			var castState = state as ITextureState;
			_unloadQueue.Add(castState);
		}

		/// <summary>
		/// Updates the state.
		/// </summary>
		/// <param name="elapsed">The elapsed time since last update.
		/// </param>
		protected internal override void UpdateState(
			TimeSpan elapsed)
		{
			foreach (var state in _unloadQueue)
				_renderDevice.UnloadTexture(state);
			Future<WrappedImage> future;
			while ((future = _channel.TryDequeue()) != null)
			{
				Debug.Assert(future.IsFinished);
				Uri identifier = _mappedTexture[future];
				_mappedTexture.Remove(future);
				if (!_activeLoads.Contains(identifier) ||
				    _activeLoads[identifier] != future)
					continue;
				_activeLoads.Remove(identifier);
				if (future.Failure != null)
				{
					if (_log.IsWarnEnabled)
					{
						_log.Warn(
							"Error occurred while loading a texture (" +
							identifier + ") " + "with a DefaultTextureFactory.",
							future.Failure);
					}
					continue;
				}
				WrappedImage result = future.Result;
				Debug.Assert(result != null);
				ITextureState state = _renderDevice.LoadTexture(result);
				Debug.Assert(state != null);
				SetState(identifier, state, false);
				if (_log.IsDebugEnabled)
				{
					_log.Debug("Texture (" + identifier +
					           ") loaded successfully.");
				}
			}
		}
	}

	/// <summary>
	/// Proxy creator for the default texture factory.
	/// </summary>
	[Serializable]
	internal sealed class DefaultTextureFactoryProxyCreator :
		AbstractResourceProxyCreator<ITextureResource, Texture>
	{
		/// <summary>
		/// Creates a local proxy for the resource with the specified
		/// identifier.
		/// </summary>
		/// <param name="identifier">The identifier of the resource.</param>
		/// <returns>The new proxy.</returns>
		public override Texture Create(Uri identifier)
		{
			return new Texture(identifier);
		}
	}
}